  /*********************************************
 * OPL 12.5.1 Model
 * Author: Yosef Moatti
 * Creation Date: July 18, 2013
 * 
 *
 *********************************************/

/* Sets the relative approximation gap to 1.5% */  
 execute {
    cplex.epgap = 0.015;
 };
 
 /* OPL_INPUT_1: the weights for various optimization subgoals: */ 
 int INCOME_ON_LOAD = ...;   // how many dolars are gained by meeting one unit of CPU demand. Assumption is that the cost of one unit of electricity is 1
 int PLACEMENT_WEIGHT = ...;       // TODO MODIFY to 100
 int LOAD_BALANCE_WEIGHT = ...;

 /* OPL_INPUT_2: the various arrays or matrixes dimensions */
 int nbHosts = ...; 	// index j - Number of host
 int nbVM = ...; 		// index i - Number of VMs
 int nbResources = ...; // index k - Number of resources
 int nbPieces = ...;      // the number of pieces in the loadCost piecewise linear function

 int CPU  = 1;         //resource index for CPU
 
 range HOSTS = 1..nbHosts;
 range VMs = 1..nbVM;
 range RESOURCEs = 1..nbResources; //CPU is resource number 1 and CORE_PERCENT is resource number 2
 range RESOURCESWithoutCPU = 2..nbResources;
 range PIECEs  = 1..nbPieces;  
 range PIECEsNoLAST  = 1..nbPieces-1;  

 /* OPL_INPUT_3 single dimensional arrays of int */
 int vsCpuDemand[VMs] = ...;  // estimated demand for CPU in each VM
 int fixed[VMs] = ...;  // Valued 1 if the VM has fixed placement
 float loadCostSlopes[PIECEs] = ...;  // the nbPieces cost slopes for the CPU load cost function
 float loadBreakpoints[PIECEsNoLAST] = ...; // the nbPieces-1 knee points for the CPU load cost function
 int loadBalancingActivated = ...; // Input value is either 1 (normal) or 0 in case total demand exceeds capacity, in which case we disable the load balancing mechanism

 /* OPL_INPUT_4 2 dimensional arrays of int / float */
 int previousPlacement[VMs][HOSTS] = ...; // Matrix of 0-1s representing the last placement
 int capacity[HOSTS][RESOURCEs] = ...;  // capacity of each host for each resource 
 int reservation[VMs][RESOURCEs] = ...;  // requested resource for each VM, if this minimum is not met the VM cannot be instantiated 
 
 float gradient = ...;  // the cloud total population ration gradient (that is total reservations divided by total hosts capacities, including powered off hosts)
 int numberOfRelocation = ...; // Max number of allowed relocations.
 float hostBaseEnergyConsumption[HOSTS] = ...; // the host energy consumption (for one time unit)
 float hostPowerOnEnergyConsumption[HOSTS] = ...; // the cost of powering off/on a given host
 int inputActiveHosts[HOSTS] = ...;        // input values :activeHosts[h] = 1 <==> host index h is powered on in the input data
 
 // For OPL (uses Tuples): 
 
 /*********************************************
 *		Decision VARiables
 *********************************************/
 
  /* OPL_DVAR 1  Output variables */
 dvar int+ targetPlacement[VMs][HOSTS] in 0..1;  // output of the placement algorithm
 dvar int+ targetActiveHost[HOSTS] in 0..1; // output values: activeHosts[h] = 1 <==> host index h is powered on 
 dvar float+ cpu_allocation[VMs][HOSTS];  //allocation only for CPU     

 /* OPL_DVAR 2 Intermediary variables */
 dvar int+ placedInTarget[VMs] in 0..1;    // 1 <==> VM_i is placed in targetPlacement
 dvar int+ vmContainedHost[HOSTS];         // Number of VMs hosted in a given host:
 dvar int+ emptyHost[HOSTS] in 0..1;       // 1 <==> Host is empty
 dvar float+ targetLoadBalancingScore;         // total cost of overloaded host costs
 dvar float+ cpu_allocationOnHost[HOSTS];  // total allocation of CPU on host
 dvar float+ cpu_currentAllocationOnHost[HOSTS];  // total current allocation of CPU on host
 dvar float+ hostCpuLoad[HOSTS];           // the relative CPU load on hosts
 dvar float+ currentHostCpuLoad[HOSTS];           // the current relative CPU load on hosts
 dvar int+ changeMatrix[VMs][HOSTS] in 0..1;  // 1 <==> if in position <j,i> there has been a change (i.e. there was a VM and now is not there anymore or there is a VM which was not there before
 dvar int+ vmRelocated[VMs] in 0..1;       // !=0 if VM i has been relocated 
 dvar float+ totalPlacedVMs;  //total number of placed VMs
 dvar int+ emptyHosts; 
 
 dvar float+ totalEnergyCost; // Total electricity cost for all the hosts
 dvar float+ totalAllocation; // Total CPU allocation in the cloud
 dvar float+ idealLoadBalancingScore; // LB score if the LB was ideally spread over all the entry active hosts
 dvar float+ currentLoadBalancingScore; // LB score with current placement

 /*********************************************
 *		EXPRESSIONS
 *********************************************/
  /* OPL_EXP_1 Expression */
 dexpr int nbInputActiveHosts = sum(h in HOSTS) inputActiveHosts[h];
 dexpr float totalDemand  =  sum(v in VMs) vsCpuDemand[v];  // the total estimated CPU demand
 dexpr float totalCPUCapacity = sum(h in HOSTS) inputActiveHosts[h] * capacity[h][CPU];  // the available CPU capacity in (input) active hosts
 dexpr float idealLoad = totalDemand/totalCPUCapacity;
 
 
// dexpr float currentLoadBalancingScore222 = (sum(h in HOSTS)
//	    		piecewise(i in PIECEsNoLAST) { 
//		    	  loadCostSlopes[i] -> loadBreakpoints[i];
//			      loadCostSlopes[nbPieces] 
//			    }  currentHostCpuLoad[h]);
// 
// dexpr float idealLoadBalancingScore222 = nbInputActiveHosts * piecewise(i in PIECEsNoLAST) { 
//		    	  loadCostSlopes[i] -> loadBreakpoints[i];
//			      loadCostSlopes[nbPieces] 
//			    }  idealLoad;
// 
// 
// dexpr float XXX = 1.0 * (currentLoadBalancingScore222 - idealLoadBalancingScore222);

/*********************************************
 *		OBJECTIVE FUNCTION
 *********************************************/
 
 maximize 	
		 	- INCOME_ON_LOAD * 35000 * (1.0 - (totalAllocation / totalDemand)) // punishes non met demand  
 		 	- PLACEMENT_WEIGHT * (nbVM - totalPlacedVMs) * (totalDemand/nbVM) * INCOME_ON_LOAD  // punishes non placed VMs
		 	- totalEnergyCost  // punishes electricity costs
            - (LOAD_BALANCE_WEIGHT * nbInputActiveHosts * loadBalancingActivated * (targetLoadBalancingScore - idealLoadBalancingScore)) // punishes load imbalance
  		    ;

 /*********************************************
 *		CONSTRAINTS
 *********************************************/
 
  subject to {

     	// constraint #4
    	// each VM is placed at most in one host. placed[v] is defined as a binary variable
    	forall(v in VMs)
      		singleHostConstraint: 
   	  		placedInTarget[v] == sum(h in HOSTS) targetPlacement[v][h]; 	  

    	// constraint #5
    	// cpu allocation cannot be greater than reservation or demand
    	forall(v in VMs, h in HOSTS)  
    	cpuAllocationConstraint2:
  			cpu_allocation[v][h] <= vsCpuDemand[v] * targetPlacement[v][h];

		// constraint #6
		// have vmContainedHost[h] reflect the exact number of VMs placed in h
    	forall(h in HOSTS)
	  	VMContainedConstraint:
			vmContainedHost[h] == sum(v in VMs) targetPlacement[v][h]; 

		// constraint #7
		// force emptyHost[h] to be 1.0 if h is empty, else 0.0
    	forall(h in HOSTS)
	  	EmptyHostConstraint:
	  		emptyHost[h] <= 1.0 - (vmContainedHost[h]/card(VMs));

		// constraint #8
		// Set emptyHosts to its value
    	forall(h in HOSTS)
	  	EmptyHostConstraint2:
	  		emptyHosts == sum(h in HOSTS) emptyHost[h];

    	// constraint #9
   	    // fixed VMs have imposed placement => fix the placement variable
    	forall(v in VMs, h in HOSTS)  
      		noChoiceConstraint:
       			targetPlacement[v][h] * fixed[v] == previousPlacement[v][h] * fixed[v] ;

    	// constraint #11
   	    // enforce Capacity limits  
    	CapacityConstraint:
    	forall(h in HOSTS, k in RESOURCESWithoutCPU)
    		sum(v in VMs) targetPlacement[v][h] * reservation[v][k] <= capacity[h][k];
          
    	// constraint #12
   	    // sets cpu_allocationOnHost[h] to the sum all VM cpu allocations
    	forall(h in HOSTS)
	  	allocationOnHostConstraint:
			cpu_allocationOnHost[h] ==  sum(v in VMs) cpu_allocation[v][h];

    	// constraint #12
   	    // sets cpu_allocationOnHost[h] to the sum all VM cpu allocations
    	forall(h in HOSTS)
	  	currentAllocationOnHostConstraint:
			cpu_currentAllocationOnHost[h] ==  sum(v in VMs) previousPlacement[v][h] * vsCpuDemand[v];

    	// constraint #14
   	    // constraints VM allocations so as to abide with host capacity and margins
    	forall(h in HOSTS)
        	capacityConstraintOnCPU:
        		cpu_allocationOnHost[h] <= capacity[h][CPU];        


    	// constraint #14B
    	forall(h in HOSTS)
        	hostCpuLoadConstraint:
        		hostCpuLoad[h] == cpu_allocationOnHost[h] / capacity[h][CPU];        

    	// constraint #14C
    	forall(h in HOSTS)
        	hostCurrentCpuLoadConstraint:
        		currentHostCpuLoad[h] == cpu_currentAllocationOnHost[h] / capacity[h][CPU];    

    	// constraint #15
   	    // Prevents VMs which have negative placement priorities to be placed
    	forall(v in VMs)
      		priorityConstraint:
        		placedInTarget[v] >= 0;   
      
    	// constraint #18
   	    // Define the change matrix
     	forall(h in HOSTS, v in VMs)
	    	changeMatrixConstraint:
			changeMatrix[v][h] == targetPlacement[v][h] + previousPlacement[v][h] - (2 * targetPlacement[v][h] * previousPlacement[v][h]);	

    	// constraint #19
   	    // Sets vmRelocated[v] to 1.0 if it was relocated, else it will be valued 0.0
     	forall(v in VMs)
		relocatedConstraint:
 			vmRelocated[v] >= ( sum(h in HOSTS)  changeMatrix[v][h] ) / 2.0;

    	// constraint #19B
    	// Limit the total number of relocations to numberOfRelocation 
 		(sum(v in VMs) vmRelocated[v]) <= numberOfRelocation;

    	// constraint #25
   	    // force the global totalPlacedVMs to be sum of all placement (relative) priorities for the placed VMs  
	    totalPlacedConstraint:  
			totalPlacedVMs == sum(v in VMs) placedInTarget[v];
				
		// constraint #26
		// computes the global CPU load balancing cost as the sum of a piecewise linear function for each of the hosts	
	       loadBalancingConstraint2:
		      targetLoadBalancingScore == (sum(h in HOSTS)
	    		piecewise(i in PIECEsNoLAST) { 
		    	  loadCostSlopes[i] -> loadBreakpoints[i];
			      loadCostSlopes[nbPieces] 
			    }  hostCpuLoad[h]);

		// constraint #26
		// computes the global CPU load balancing cost as the sum of a piecewise linear function for each of the hosts	
	       loadBalancingConstraint2B:
		      currentLoadBalancingScore == (sum(h in HOSTS)
	    		piecewise(i in PIECEsNoLAST) { 
		    	  loadCostSlopes[i] -> loadBreakpoints[i];
			      loadCostSlopes[nbPieces] 
			    }  currentHostCpuLoad[h]);

        // constraint #30
		totalAllocationConstraint:
    		totalAllocation == sum(v in VMs, h in HOSTS) (cpu_allocation[v][h]);

        // constraint #31
		activeHostConstraint3:
		  forall(h in HOSTS, v in VMs)
    		targetActiveHost[h] >= targetPlacement[v][h];

       // constraint #32 - targetActiveHost[h] is constrained to 0 if host h is empty
		activeHostConstraint4:
		  forall(h in HOSTS, v in VMs)
    		targetActiveHost[h] <= vmContainedHost[h];

        // constraint #32 total energy cost is equal to the totalAllocation multiplied by 1 
        // which is the energy price for 1 unit of CPU demand + the energy overhead for all active hosts
           totalEnergyCostConstraint:
		      totalEnergyCost == totalAllocation + (sum(h in HOSTS) targetActiveHost[h] * hostBaseEnergyConsumption[h]);
		      
        // constraint #33 - restricts target placement of VMs to the subset of hosts which were initially active:
		targetPlacementConstraint:
		  forall(h in HOSTS, v in VMs)
    		targetPlacement[v][h] <= inputActiveHosts[h]; 

        idealLBConstraint:
          idealLoadBalancingScore ==  nbInputActiveHosts * piecewise(i in PIECEsNoLAST) { 
		    	  loadCostSlopes[i] -> loadBreakpoints[i];
			      loadCostSlopes[nbPieces] 
			    }  idealLoad;
		       
  }				
